/*
 *   The contents of this file are subject to the Mozilla Public License
 *   Version 1.1 (the "License"); you may not use this file except in
 *   compliance with the License. You may obtain a copy of the License at
 *   http://www.mozilla.org/MPL/
 *
 *   Software distributed under the License is distributed on an "AS IS"
 *   basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 *   License for the specific language governing rights and limitations
 *   under the License.
 *
 *   The Original Code is Matra - the DTD Parser.
 *
 *   The Initial Developer of the Original Code is Conrad S Roche.
 *   Portions created by Conrad S Roche are Copyright (C) Conrad 
 *   S Roche. All Rights Reserved.
 *
 *   Alternatively, the contents of this file may be used under the terms
 *   of the GNU GENERAL PUBLIC LICENSE Version 2 or any later version
 *   (the  "[GPL] License"), in which case the
 *   provisions of GPL License are applicable instead of those
 *   above.  If you wish to allow use of your version of this file only
 *   under the terms of the GPL License and not to allow others to use
 *   your version of this file under the MPL, indicate your decision by
 *   deleting  the provisions above and replace  them with the notice and
 *   other provisions required by the GPL License.  If you do not delete
 *   the provisions above, a recipient may use your version of this file
 *   under either the MPL or the GPL License."
 *
 *   [NOTE: The text of this Exhibit A may differ slightly from the text of
 *   the notices in the Source Code files of the Original Code. You should
 *   use the text of this Exhibit A rather than the text found in the
 *   Original Code Source Code for Your Modifications.]
 *
 * Created: Conrad S Roche <derupe at users.sourceforge.net>,  25-Jul-2000
 */

package com.conradroche.matra.data;

/**
 * Utility class to parse a String.
 * 
 * @author Conrad Roche
 */
public class Data {

//CR: TODO: Consider having a begin pos and end pos - so that substrings of
//Data does not create new String objects - but just populates these two ints.

	/** 
	 * String data being parsed.
	 */
	private String data;
	
	/**
	 * Current parsing position as measured from the beginning 
	 * of the String data. 
	 * For example a value of '0' will indicate that the first char
	 * is yet to be read. '1' will indicate that the first char is 
	 * read and the second char is to be read. 
	 */
	private int currPos = 0;
	
	/**
	 * Current parsing position measured in row and column.
	 * A value of 1 for the row will indicate that the first
	 * row is being read.
	 * A value of 1 for the clm will indicate that the first
	 * char in the current row is read.
	 */
	private int row = 1, clm = 0;

	/**
	 * Location of the first char (inclusive) from which to start reading.
	 * <p/>
	 * <pre>
	 * 0         1         2         3         4         5         6
	 * 01234567890123456789012345678901234567890123456789012345678901234
	 * The complete string containing the string to be parsed within it.
	 *                                ^begin pos            ^end pos 
	 * </pre>
	 * <p/>
	 * For the above example, begin will be 31 and end will be 53.
	 * Reading the above string will return "the string to be parsed".
	 */
	private int begin = 0;
	
	/**
	 * Location of the last character (inclusive) until which reading is allowed.
	 */
	private int end   = -1;
	
	/**
	 * Denotes end of data stream.
	 * This is used in the lookNextChar and getPreviousChar 
	 * methods when at the edge of the data. 
	 */
	public static final char END_OF_DATA_CHAR = Character.MAX_VALUE;


	/**
	 * Denotes that the char searched was found.
	 */
	public static final int FOUND_CHAR = 0;
	 
	/**
	 * Denotes that the char searched was not found.
	 */
	public static final int CHAR_NOT_FOUND = -1;
	 
	/**
	 * Data constructor.
	 */
	public Data() {
		super();
		data = "";
		//CR: TODO: Should this constructor be private; or do we add methods to set the data - for reusing the instance.
		//probably the latter. see #reset() 
	}

	/**
	 * Data copy constructor.
	 * 
	 * @param newData The Data object to copy from.
	 */
	public Data(Data newData) {
		
		if(newData != null) {
			this.data = newData.data;
			this.row  = newData.row;
			this.clm  = newData.clm;
			this.currPos = newData.currPos;
			this.begin = newData.begin;
			this.end   = newData.end; 
		} else {
			row = 0;
			data = null;
		}
	}

	/**
	 * Data Constructor.
	 * 
	 * @param strData The data to be parsed.
	 */
	public Data(String strData) {
		
		data = strData;
		
		if(strData != null) {
			end  = strData.length() - 1;
		} else {
			row = 0;
		}
	}
	
	/**
	 * Check if the end of the data stream is reached.
	 * 
	 * @return <code>true</code> of end of data is reached; <code>false</code> if end of data is not reached.
	 */
	public boolean endOfData() {
	
		if( currPos > end ) {
			return true;
		}
	
		return false;
	}
	/**
	 * Returns the current location in 
	 * terms of rows and columns.
	 *  
	 * @return The current location.
	 */
	public CursorLocation getCurrentLocation() {
		return new CursorLocation(row, clm, currPos);
	}
	
	/**
	 * Returns the current row being read.
	 * @return The current row.
	 */
	public int getRow() {
		return row;
	}
	
	/**
	 * Returns the current column being read.
	 * @return The current column.
	 */
	public int getColumn() {
		return clm;
	}

	/**
	 * Get the current reading position in the Data stream - 
	 * as measured in the number of chars from the beginning
	 * of the data.
	 * 
	 * @return The current position.
	 */
	public int getCurrentPosition() {
		return currPos;
	}
	/**
	 * Read the next char from the data stream.
	 * 
	 * @return The next char.
	 */
	public char getNextChar() {
	
		char nextChar = lookNextChar();
		
		step(nextChar);
			
		return nextChar;
	}
	
	/**
	 * Take a step ahead into the data stream.
	 * 
	 * @param thisChar The char that was read at the current position.
	 */
	private final void step(char thisChar) {
		//CR: TODO: Rename this method
		
		if(thisChar == Data.END_OF_DATA_CHAR) {
			return;
		}
		
		currPos++;
	
		if(thisChar == '\n') {
			row++;
			clm = 0;
		}
		else {
			clm++;
		}
	}
	
	/**
	 * Conditional read from the data stream.
	 * <p/>
	 * Checks if the next char is the one specified.
	 * If the next char is the char specified, move
	 * ahead by one char (i.e. read the char).
	 * 
	 * @param lookupChar The char to look for.
	 * 
	 * @return  Returns <code>FOUND_CHAR</code> if the next 
	 * char is the one specified, returns 
	 * <code>CHAR_NOT_FOUND</code> if not.
	 */
	public int checkNextChar(char lookupChar) {
	
		//CR: TODO: Why isn't this returning a boolean???
		
		char nextChar = lookNextChar();
		
		if(nextChar != lookupChar) {
			return CHAR_NOT_FOUND;
		}
		
		step(nextChar);
			
		return FOUND_CHAR;
	}
	/**
	 * Get the previous char read from the data stream.
	 * 
	 * @return The previous char.
	 */
	public char getPrevChar() {
	
		if(data == null || data.length() == 0) {
			return Data.END_OF_DATA_CHAR;
		}
			
		if( endOfData() ) {
			return data.charAt(end);
		}
	
		if(currPos == begin) {
			return Data.END_OF_DATA_CHAR;
		}
		
		char prevChar = data.charAt( currPos - 1 );
			
		return prevChar;
	}
	
	/**
	 * Get the unread part of the data stream.
	 * 
	 * @return The remaining data string.
	 */
	public String getRemaining() {
		
		//CR: TODO: Return Data object 
	
		if(data == null)
			return null;
	
		if( currPos > end )
			return null;
	
		return data.substring(currPos); //CR: TODO: Check the effect of 'end' here
		
	}
	/**
	 * Get the total length of the data stream.
	 * 
	 * @return Total length of data.
	 */
	public int length() {
		return (end - begin + 1);
	}
	/**
	 * Peek at the next char to be read.
	 * 
	 * @return The next unread character in the data.
	 */
	public char lookNextChar() {
	
		if( endOfData() )
			return Data.END_OF_DATA_CHAR;
	
		char nextChar = data.charAt(currPos);
			
		return nextChar;
	}
	
	/**
	 * Peek ahead n number of chars from the data.
	 * 
	 * @param numChars Number of characters to peek ahead. 
	 * 			<code>0</code> will return current char, 
	 * 			<code>1</code> will return next char
	 * 
	 * @return The peeked char.
	 */
	public char peekAhead(int numChars) {

		if( numChars < 0 || endOfData() || (currPos + numChars - 1) > end
			|| (currPos + numChars - 1) < begin) {

			return Data.END_OF_DATA_CHAR;
		}
	
		char nextChar = data.charAt(currPos + numChars - 1);
			
		return nextChar;
	}
	
	/**
	 * Reset the data for use.
	 */
	public void reset() {
	
		data = "";
		currPos = 0;
		row = 1;
		clm = 0;
		begin = 0;
		end = -1;
	}
	/**
	 * Move the current read position back to the
	 * beginning of the data stream.
	 */
	public void rewind() {
		
		currPos = begin;
		//CR: TODO: Do we store the initial row & clm?
		row = 1;
		clm = 0;
		begin = 0;
		
		if(data != null) {
			end = data.length() - 1;
		} else {
			end = -1;
			row = 0;
		}
	}
	/**
	 * Return the String representation of the data stream.
	 * @return The string data.
	 */
	public String toString() {
		
		if(data == null) {
			return null;
		}
		
		if(end == (data.length() - 1) && begin == 0) {
			return data;
		}
		return data.substring(begin, end + 1);
	}
}